/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class GroupLevelEnumerator : ConversionEnumerator<DataPath>, IGroupEnumerator
  {
    #region Static Fields

    private readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private readonly Guid JumpMoveAndFetchCoreCallId = DataUtils.CreateCallId();
    private readonly Guid GetCurrentItemHeadersAndContentCallId = DataUtils.CreateCallId();
    private readonly Guid GetPreviousItemFootersCallId = DataUtils.CreateCallId();
    private readonly Guid GetHeadersFootersForItemsCallId = DataUtils.CreateCallId();


    private readonly Guid CountCallId = DataUtils.CreateCallId();
    private readonly Guid ResolveCountCallId = DataUtils.CreateCallId();
    private readonly Guid SubCountCallId = DataUtils.CreateCallId();
    private readonly Guid SubGroupCountsCallId = DataUtils.CreateCallId();
    private readonly Guid RangeCountCallId = DataUtils.CreateCallId();
    private readonly Guid HeadersFootersCountCallId = DataUtils.CreateCallId();

    #endregion


    internal GroupLevelEnumerator( IAsyncEnumerator<DataPath> childEnumerator, GroupHelper groupHelper )
      : base(
         childEnumerator,
         ( item ) => ( DataPath )groupHelper.ConvertLocal( item ),
         groupHelper.ConvertBackLocal )
    {
      if( groupHelper == null )
        throw new ArgumentNullException( "groupHelper" );

      m_groupHelper = groupHelper;
    }

    #region GroupHelper Property

    public GroupHelper GroupHelper
    {
      get
      {
        return m_groupHelper;
      }
    }

    #endregion GroupHelper Property

    #region GroupHandler Property

    public GroupHandler GroupHandler
    {
      get
      {
        return this.GroupHelper.GroupHandler;
      }
    }

    #endregion GroupHandler Property

    #region CachedPathProvider Property

    internal Func<DataPath, DataPath> CachedPathProvider
    {
      get;
      set;
    }

    #endregion CachedPathProvider Property

    #region AddOrRemoveItems Property

    internal bool AddOrRemoveItems
    {
      get
      {
        return this.GroupHandler.HasHeadersFooters
          || this.GroupHandler.HasCollapsedGroups;
      }
    }

    #endregion AddOrRemoveItems Property

    #region GroupCountPolicy Property

    private GroupCountPolicy GroupCountPolicy
    {
      get
      {
        return this.GroupHelper.GroupHandler.GroupCountPolicy;
      }
    }

    #endregion GroupCountPolicy Property

    #region Begin/End JumpMoveAndFetch Methods

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      JumpMoveAndFetchAsyncResult mainAsyncResult = new JumpMoveAndFetchAsyncResult( this.JumpMoveAndFetchCallId, userCallback, asyncState, parameters );

      if( !this.AddOrRemoveItems )
      {
        mainAsyncResult.Start(
          ( callback, state ) => this.BaseBeginJumpMoveAndFetch( parameters, callback, state ),
          ( parentAr, childAr ) => new DependentCallResult( this.BaseEndJumpMoveAndFetch( childAr ) ) );
      }
      else if( parameters.ExpectedMoveCount != 0
        && ( parameters.ExpectedFetchCount == 0
        || parameters.GetMoveUnit() != parameters.GetFetchUnit() ) )
      {
        JumpMoveAndFetchParameters moveParameters = parameters.CreateWithNewFetchCount( parameters.GetMoveUnit() );

        mainAsyncResult.Start(
          ( callback, state ) => this.BeginJumpMoveAndFetchCore( moveParameters, callback, state ),
            this.TwoWayMoveCallback );
      }
      else
      {
        mainAsyncResult.Start(
          ( callback, state ) => this.BeginJumpMoveAndFetchCore( parameters, callback, state ),
          ( parentAr, childAr ) => new DependentCallResult( this.EndJumpMoveAndFetchCore( ( JumpMoveAndFetchAsyncResult )childAr ) ) );
      }

      return mainAsyncResult;
    }

    public override JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      jumpMoveAndFetchAsyncResult.ValidateCallEnd( this.JumpMoveAndFetchCallId );
      return jumpMoveAndFetchAsyncResult.GetCallResult<JumpMoveAndFetchResult<DataPath>>();
    }

    private DependentCallResult TwoWayMoveCallback( AsyncResult mainAr, IAsyncResult subFetchAr )
    {
      JumpMoveAndFetchAsyncResult mainAsyncResult = ( JumpMoveAndFetchAsyncResult )mainAr;
      JumpMoveAndFetchParameters mainAsyncParams = mainAsyncResult.GetParameters();

      JumpMoveAndFetchResult<DataPath> moveResult = this.EndJumpMoveAndFetchCore( ( JumpMoveAndFetchAsyncResult )subFetchAr );

      DependentCallResult callResult;

      if( !moveResult.Result )
      {
        callResult = new DependentCallResult(
          new JumpMoveAndFetchResult<DataPath>(
            mainAsyncResult,
            moveResult.ActualMoveCount,
            moveResult.FetchedItems,
            moveResult.Result,
            moveResult.EndPositionObject,
            moveResult.EndPositionOffset,
            moveResult.TotalCount ) );
      }
      else if( mainAsyncParams.ExpectedFetchCount == 0 )
      {
        callResult = new DependentCallResult(
          new JumpMoveAndFetchResult<DataPath>(
            mainAsyncResult,
            moveResult.ActualMoveCount,
            new DataPath[ 0 ],
            moveResult.Result,
            moveResult.EndPositionObject,
            moveResult.EndPositionOffset,
            moveResult.TotalCount ) );

      }
      else
      {
        JumpMoveAndFetchParameters fetchParameters = new JumpMoveAndFetchParameters(
          moveResult.EndPositionObject,
          0,
          mainAsyncParams.ExpectedFetchCount,
          false );

        callResult = new DependentCallResult( null,
          ( callback, state ) => this.BeginJumpMoveAndFetchCore( fetchParameters, callback, state ),
          this.TwoWayFetchCallback );
      }

      return callResult;
    }

    private DependentCallResult TwoWayFetchCallback( AsyncResult mainAr, IAsyncResult subFetchAr )
    {
      JumpMoveAndFetchAsyncResult mainAsyncResult = ( JumpMoveAndFetchAsyncResult )mainAr;

      JumpMoveAndFetchResult<DataPath> fetchResult = this.EndJumpMoveAndFetchCore( ( JumpMoveAndFetchAsyncResult )subFetchAr );

      return new DependentCallResult(
        new JumpMoveAndFetchResult<DataPath>(
          mainAsyncResult,
          mainAsyncResult.ExpectedMoveCount,
          fetchResult.FetchedItems,
          fetchResult.Result,
          fetchResult.EndPositionObject,
          fetchResult.EndPositionOffset,
          fetchResult.TotalCount ) );
    }

    private JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetchCore( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      GroupAsyncResult asyncResult = new GroupAsyncResult( this.JumpMoveAndFetchCoreCallId, userCallback, asyncState, parameters );

      asyncResult.Start(
        ( callback, state ) => this.BaseBeginJumpMoveAndFetch( this.OptimizeParameters( parameters ), callback, state ),
        this.InitialFetchCallback );

      return asyncResult;
    }

    private JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetchCore( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      jumpMoveAndFetchAsyncResult.ValidateCallEnd( this.JumpMoveAndFetchCoreCallId );
      return jumpMoveAndFetchAsyncResult.GetCallResult<JumpMoveAndFetchResult<DataPath>>();
    }

    #endregion Begin/End JumpMoveAndFetch Methods

    #region Base(Begin/End)JumpMoveAndFetch methods

    private JumpMoveAndFetchAsyncResult BaseBeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    private JumpMoveAndFetchResult<DataPath> BaseEndJumpMoveAndFetch( IAsyncResult childAr )
    {
      return base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )childAr );
    }

    #endregion Base(Begin/End)JumpMoveAndFetch methods

    #region Begin/End GetGroupItems

    public IAsyncResult BeginGetBaseItems( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    public JumpMoveAndFetchResult<DataPath> EndGetBaseItems( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      return base.EndJumpMoveAndFetch( jumpMoveAndFetchAsyncResult );
    }

    #endregion

    #region Sub Enumerator Fetch Callbacks

    private DependentCallResult InitialFetchCallback( AsyncResult operationAr, IAsyncResult subFetchAr )
    {
      GroupAsyncResult groupAr = ( GroupAsyncResult )operationAr;
      JumpMoveAndFetchResult<DataPath> result = base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )subFetchAr );

      Debug.Assert( result.ExpectedFetchCount != 0 );

      ChildResultEnumerator childResult = new ChildResultEnumerator( result );

      //Handle the fail move case
      if( !childResult.HasCurrent )
        return this.CompleteOperation( groupAr, childResult );

      DependentCallResult callResult = null;
      DataPath previousPath = null;
      DataPath completeHeaderPath;
      DataPath startingPath;
      Nullable<StartPosition> startPosition;

      this.ExtractFetchSourcePath(
        groupAr.GetParameters(),
        out completeHeaderPath,
        out startingPath,
        out startPosition );

      if( completeHeaderPath != null )
      {
        Debug.Assert( groupAr.ExpectedMoveCount == 0 );

        DataPath headerPath = this.GetHeaderPath( completeHeaderPath );
        HeaderFooterDataItem headerItem = ( HeaderFooterDataItem )headerPath.LastChild;

        previousPath = completeHeaderPath;

        if( ( groupAr.ExpectedFetchCount < 0 ) == headerItem.IsHeader )
        {
          DataPath headerGroup = headerPath.ParentPath;
          DataPath currentPath = childResult.Current;

          if( headerGroup.IsAncestorOf( currentPath ) )
          {
            callResult = new DependentCallResult( null,
              ( callback, state ) => this.BeginGetHeadersFootersForItem(
                callback,
                state,
                previousPath,
                Math.Abs( groupAr.ExpectedFetchCount ),
                groupAr.GetFetchUnit(),
                headerItem.IsHeader ),
              ( parentAr, childAr ) => this.HeadersStartPositionFetchCallback( parentAr, childAr, headerPath ) );
          }
        }
      }
      else if( groupAr.ExpectedMoveCount == 0 && startingPath != null )
      {
        previousPath = ( startPosition != null )
          ? startingPath.ParentPath
          : startingPath;
      }
      else if( Math.Abs( groupAr.ExpectedMoveCount ) == 1 && groupAr.SourcePosition.IsOutOfBound )
      {
        previousPath = new DataPath( new DataItem[ 0 ] );
      }

      if( callResult == null )
      {
        callResult = new DependentCallResult( null,
          ( callback, state ) => this.BeginGetCurrentItemHeadersAndContent(
            callback,
            state,
            1,
            previousPath,
            childResult.Current,
            Math.Abs( groupAr.ExpectedFetchCount ),
            groupAr.GetFetchUnit() ),
          ( parentAr, childAr ) => this.CurrentItemHeadersAndContentFetchCallback( parentAr, childAr, childResult ) );
      }

      return callResult;

    }

    private DependentCallResult SubsequentFetchCallback( AsyncResult operationAr, IAsyncResult subFetchAr, DataPath previousPath )
    {
      GroupAsyncResult groupAr = ( GroupAsyncResult )operationAr;
      JumpMoveAndFetchResult<DataPath> result = base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )subFetchAr );

      Debug.Assert( result.ExpectedFetchCount != 0 );

      ChildResultEnumerator childResult = new ChildResultEnumerator( result );

      return new DependentCallResult( null,
        ( callback, state ) => this.BeginGetPreviousItemFooters( callback, state, 1, previousPath, childResult.Current, Math.Abs( groupAr.RemainingFetchCount ), groupAr.GetFetchUnit() ),
        ( parentAr, childAr ) => this.PreviousItemFootersCallback( parentAr, childAr, previousPath, childResult ) );
    }

    #endregion Sub Enumerator Fetch Callbacks

    #region Header start position special case

    private DependentCallResult HeadersStartPositionFetchCallback( AsyncResult operationAr, IAsyncResult headersFetchAr, DataPath headerPath )
    {
      GroupAsyncResult groupAr = ( GroupAsyncResult )operationAr;

      groupAr.AddFetchResult( this.EndGetHeadersFootersForItem( headersFetchAr ) );

      Debug.Assert( headerPath.LastChild is HeaderFooterDataItem );
      Debug.Assert( ( groupAr.ExpectedFetchCount < 0 ) == ( ( HeaderFooterDataItem )headerPath.LastChild ).IsHeader );

      DataPath groupPath = headerPath.ParentPath;

      //Fetch the next group.
      StartPosition startPosition = groupAr.GetFetchUnit() > 0
        ? StartPosition.After
        : StartPosition.Before;

      int fetchCount = groupAr.RemainingFetchCount;

      if( fetchCount == 0 )
      {
        fetchCount = groupAr.GetFetchUnit();
      }

      JumpMoveAndFetchParameters newFetch =
        new JumpMoveAndFetchParameters(
          this.GroupHelper.CreateSortPositionForGroup( groupPath, startPosition ),
          0,
          fetchCount,
          false );

      return new DependentCallResult( null,
        ( callback, state ) => this.BaseBeginJumpMoveAndFetch( newFetch, callback, state ),
        ( parentAr, childAr ) => this.SubsequentFetchCallback( parentAr, childAr, groupPath ) );

    }

    #endregion Header start position special case

    #region PreviousItem Footers Fetch Callbacks

    private DependentCallResult PreviousItemFootersCallback( AsyncResult operationAr, IAsyncResult previousItemAr, DataPath previousItem, ChildResultEnumerator childResult )
    {
      GroupAsyncResult groupAr = ( GroupAsyncResult )operationAr;

      groupAr.AddFetchResult( this.EndGetPreviousItemFooters( previousItemAr ) );

      if( !childResult.HasCurrent )
        return this.CompleteOperation( groupAr, childResult );

      return new DependentCallResult( null,
        ( callback, state ) => this.BeginGetCurrentItemHeadersAndContent( callback, state, 1, previousItem, childResult.Current, Math.Abs( groupAr.RemainingFetchCount ), groupAr.GetFetchUnit() ),
        ( parentAr, childAr ) => this.CurrentItemHeadersAndContentFetchCallback( parentAr, childAr, childResult ) );
    }

    #endregion PreviousItem Footers Fetch Callbacks

    #region CurrentItemHeadersAndContent Fetch Callback

    private DependentCallResult CurrentItemHeadersAndContentFetchCallback( AsyncResult operationAr, IAsyncResult handleItemAr, ChildResultEnumerator childResult )
    {
      GroupAsyncResult groupAr = ( GroupAsyncResult )operationAr;

      SimpleFetchResult currentFetch = this.EndGetCurrentItemHeadersAndContent( handleItemAr );
      groupAr.AddFetchResult( this.EndGetCurrentItemHeadersAndContent( handleItemAr ) );

      if( groupAr.RemainingFetchCount == 0 )
        return this.CompleteOperation( groupAr, childResult );

      DependentCallResult callResult;
      DataPath previousPath = groupAr.CumulativeFetch.Last();

      if( !childResult.MoveNext() )
      {
        //ChildResult is completely used an more items are needed. Fetch the child again.
        JumpMoveAndFetchParameters newFetch =
          new JumpMoveAndFetchParameters(
            new ItemPosition( previousPath ),
            groupAr.GetFetchUnit(),
            groupAr.RemainingFetchCount,
            false );

        callResult = new DependentCallResult(
          null,
          ( callback, state ) => this.BaseBeginJumpMoveAndFetch( this.OptimizeParameters( newFetch ), callback, state ),
          ( parentAr, childAr ) => this.SubsequentFetchCallback( parentAr, childAr, previousPath ) );
      }
      else
      {
        callResult = new DependentCallResult( null,
        ( callback, state ) => this.BeginGetPreviousItemFooters( callback, state, 1, previousPath, childResult.Current, Math.Abs( groupAr.RemainingFetchCount ), groupAr.GetFetchUnit() ),
        ( parentAr, childAr ) => this.PreviousItemFootersCallback( parentAr, childAr, previousPath, childResult ) );
      }

      return callResult;
    }

    #endregion CurrentItemHeadersAndContent Fetch Callback

    #region Begin/End CurrentItemHeadersAndContent Methods

    private IAsyncResult BeginGetCurrentItemHeadersAndContent( AsyncCallback userCallback, object asyncState,
      int depth, DataPath previousItem, DataPath currentItem, int absoluteFetchCount, int fetchDirection )
    {
      if( currentItem == null )
        throw new ArgumentNullException( "currentItem" );

      if( currentItem.Depth <= this.GroupHandler.GroupDescriptions.Count )
        throw new ArgumentException( "The specified current item must be an expanded item.", "currentItem" );

      if( absoluteFetchCount < 0 )
        throw new ArgumentOutOfRangeException( "absoluteFetchCount", "absoluteFetchCount must be greater than or equal to zero."  );

      if( Math.Abs( fetchDirection ) != 1 )
        throw new ArgumentException( "fetchDirection must be 1 or -1.", "fetchDirection" );

      AsyncResult asyncResult = new AsyncResult( this.GetCurrentItemHeadersAndContentCallId, userCallback, asyncState );

      DataPath previousLevel = ( previousItem != null )
        ? previousItem.CreateAncestorOrSamePath( Math.Min( depth, previousItem.Depth ) )
        : null;

      DataPath currentLevel = currentItem.CreateAncestorOrSamePath( depth );

      if( this.GroupHandler.IsCollapsed( currentLevel ) )
      {
        IEnumerable<DataPath> items;
        EstimateInt64 count;
        if( previousLevel == null || !object.Equals( previousLevel, currentLevel ) )
        {
          items = ( absoluteFetchCount != 0 )
            ? new DataPath[] { currentLevel }
            : new DataPath[ 0 ];

          count = 1;
        }
        else
        {
          items = new DataPath[ 0 ];
          count = 0;
        }
        asyncResult.StartSync( new SimpleFetchResult( items, count ) );
      }
      else
      {
        SimpleFetchResult fetchResult = new SimpleFetchResult();

        if( previousLevel != null )
        {
          DataPath getHeaderPath = null;

          if( !object.Equals( previousLevel, currentLevel ) )
          {
            //Normal case
            getHeaderPath = currentLevel;
          }
          else if( previousItem.Depth == ( depth + 1 ) && previousItem.LastChild is HeaderFooterDataItem )
          {
            //Handle the case where the previous item is a Header. Include it in the fetch.
            getHeaderPath = previousItem;

            Debug.Assert( ( ( HeaderFooterDataItem )previousItem.LastChild ).IsHeader == ( fetchDirection > 0 ) );
          }

          if( getHeaderPath != null )
          {
            Func<AsyncCallback, object, IAsyncResult> getHeaderFooters =
              ( callback, state ) =>
              {
                int remainingFetchCount = absoluteFetchCount - fetchResult.FetchedItems.Count;
                return this.BeginGetHeadersFootersForItem( callback, state, getHeaderPath, remainingFetchCount, fetchDirection, ( fetchDirection > 0 ) );
              };

            Func<AsyncResult, IAsyncResult, DependentCallResult> getHeadersFootersCallback =
              ( parentAr, childAr ) =>
              {
                fetchResult.Add( this.EndGetHeadersFootersForItem( childAr ) );
                return new DependentCallResult( fetchResult );
              };

            asyncResult.AddDependentCall( getHeaderFooters, getHeadersFootersCallback );
          }
        }

        Func<AsyncCallback, object, IAsyncResult> getItemContent;
        Func<AsyncResult, IAsyncResult, DependentCallResult> getItemContentCallback;

        if( depth < this.GroupHandler.GroupDescriptions.Count )
        {
          getItemContent = ( callback, state ) =>
          {
            int remainingFetchCount = absoluteFetchCount - fetchResult.FetchedItems.Count;
            return this.BeginGetCurrentItemHeadersAndContent( callback, state, depth + 1, previousItem, currentItem, remainingFetchCount, fetchDirection );
          };

          getItemContentCallback = ( parentAr, childAr ) =>
          {
            fetchResult.Add( this.EndGetCurrentItemHeadersAndContent( childAr ) );
            return new DependentCallResult( fetchResult );
          };
        }
        else
        {
          getItemContent = ( callback, state ) =>
          {
            AsyncResult getContentAsyncResult = new AsyncResult( Guid.Empty, null, null );
            getContentAsyncResult.StartSync( null );
            return getContentAsyncResult;
          };

          getItemContentCallback = ( parentAr, childAr ) =>
          {
            if( absoluteFetchCount > fetchResult.FetchedItems.Count )
            {
              fetchResult.Add( new DataPath[] { currentItem }, 1 );
            }
            else
            {
              fetchResult.Add( new DataPath[ 0 ], 1 );
            }
            return new DependentCallResult( fetchResult );
          };
        }

        asyncResult.AddDependentCall( getItemContent, getItemContentCallback );

        asyncResult.Start();
      }

      return asyncResult;
    }

    private SimpleFetchResult EndGetCurrentItemHeadersAndContent( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( this.GetCurrentItemHeadersAndContentCallId );
      return asyncResult.GetCallResult<SimpleFetchResult>();
    }

    #endregion Begin/End CurrentItemHeadersAndContent Methods

    #region Begin/End GetPreviousItemFooters Methods

    private IAsyncResult BeginGetPreviousItemFooters( AsyncCallback userCallback, object asyncState,
      int depth, DataPath previousItem, DataPath currentItem, int absoluteFetchCount, int fetchDirection )
    {
      AsyncResult asyncResult = new AsyncResult( this.GetPreviousItemFootersCallId, userCallback, asyncState );
      SimpleFetchResult fetchResult = new SimpleFetchResult();

      if( depth > previousItem.Depth - 1 )
      {
        asyncResult.StartSync( fetchResult );
        return asyncResult;
      }

      DataPath currentLevel = ( currentItem != null ) ? currentItem.CreateAncestorPath( depth ) : null;
      DataPath previousLevel = previousItem.CreateAncestorPath( depth );

      if( depth < ( previousItem.Depth - 1 ) )
      {
        Func<AsyncCallback, object, IAsyncResult> getSubLevelItemFooters =
          ( callback, state ) => this.BeginGetPreviousItemFooters( callback, state, depth + 1, previousItem, currentItem, absoluteFetchCount, fetchDirection );

        Func<AsyncResult, IAsyncResult, DependentCallResult> getSubLevelItemFootersCallback =
          ( parentAr, childAr ) =>
          {
            fetchResult.Add( this.EndGetPreviousItemFooters( childAr ) );

            return new DependentCallResult( fetchResult );
          };

        asyncResult.AddDependentCall( getSubLevelItemFooters, getSubLevelItemFootersCallback );
      }

      if( !object.Equals( previousLevel, currentLevel ) )
      {
        asyncResult.AddDependentCall(
          ( callback, state ) =>
          {
            int remainingFetchCount = ( absoluteFetchCount - fetchResult.FetchedItems.Count );
            return this.BeginGetHeadersFootersForItem( callback, state, previousLevel, remainingFetchCount, fetchDirection, ( fetchDirection < 0 ) );
          },
          ( parentAr, childAr ) =>
          {
            fetchResult.Add( this.EndGetHeadersFootersForItem( childAr ) );
            return new DependentCallResult( fetchResult );
          } );
      }

      if( asyncResult.HasDependentCalls )
      {
        asyncResult.Start();
      }
      else
      {
        asyncResult.StartSync( fetchResult );
      }

      return asyncResult;
    }

    private SimpleFetchResult EndGetPreviousItemFooters( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( this.GetPreviousItemFootersCallId );
      return asyncResult.GetCallResult<SimpleFetchResult>();
    }

    #endregion Begin/End GetPreviousItemFooters Methods

    #region Begin/End GetHeadersFootersForItem Methods

    private IAsyncResult BeginGetHeadersFootersForItem( AsyncCallback userCallbak, object asyncState, DataPath path, int absoluteFetchCount, int fetchDirection, bool headers )
    {
      if( fetchDirection != 1 && fetchDirection != -1 )
        throw new ArgumentException( "fetchDirection must be 1 or -1.", "fetchDirection" );

      AsyncResult asyncResult = new AsyncResult( this.GetHeadersFootersForItemsCallId, userCallbak, asyncState );

      //Handle the case where the path refer to an header/footer item.
      bool pathReferToHeaderFooter = path.LastChild is HeaderFooterDataItem;

      DataPath groupPath = path;

      if( pathReferToHeaderFooter )
      {
        groupPath = groupPath.ParentPath;

        if( headers != ( ( HeaderFooterDataItem )path.LastChild ).IsHeader )
          throw new DataInternalException();
      }

      Debug.Assert( groupPath.LastChild is GroupDataItem );

      IAsyncEnumerator<DataPath> headersFootersEnum = ( headers )
        ? this.GroupHandler.GetHeadersDataPathEnumerator( groupPath )
        : this.GroupHandler.GetFootersDataPathEnumerator( groupPath );

      int expectedFetchCount = ( absoluteFetchCount == 0 )
        ? fetchDirection
        : ( absoluteFetchCount * fetchDirection );

      long initialMoveCount;
      SourcePosition sourcePosition;

      if( pathReferToHeaderFooter )
      {
        sourcePosition = new ItemPosition( path );
        initialMoveCount = 0;
      }
      else
      {
        initialMoveCount = fetchDirection;
        sourcePosition = ( fetchDirection > 0 )
          ? SourcePosition.BeforeFirst
          : SourcePosition.AfterLast;
      }

      JumpMoveAndFetchParameters callParam = new JumpMoveAndFetchParameters( sourcePosition, initialMoveCount, expectedFetchCount );

      asyncResult.Start(
        ( callback, state ) => headersFootersEnum.BeginJumpMoveAndFetch( callParam, callback, state ),
        ( parentAr, childAr ) =>
        {
          JumpMoveAndFetchResult<DataPath> result = headersFootersEnum.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )childAr );
          SimpleFetchResult fetchResult;

          if( result.FetchedItems != null )
          {
            EstimateInt64 maxFetchCount = result.GetMaximumFetchCount();
            if( maxFetchCount != null )
            {
              maxFetchCount = maxFetchCount * result.GetFetchUnit();
            }
            else
            {
              maxFetchCount = result.TotalCount;
            }

            fetchResult = new SimpleFetchResult(
              result.FetchedItems.Take( absoluteFetchCount ),
              maxFetchCount );
          }
          else
          {
            fetchResult = new SimpleFetchResult( new DataPath[ 0 ], 0 );
          }
          return new DependentCallResult( fetchResult );
        } );

      return asyncResult;
    }

    private SimpleFetchResult EndGetHeadersFootersForItem( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( this.GetHeadersFootersForItemsCallId );
      return asyncResult.GetCallResult<SimpleFetchResult>();
    }

    #endregion Begin/End GetHeadersFootersForItem Methods

    #region Begin/End Count Methods

    public override IAsyncResult BeginCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      AsyncResult asyncResult = new AsyncResult( this.CountCallId, userCallback, asyncState );

      EstimateInt64 totalCount = null;
      EstimateInt64 collapsedCount = null;
      IList<EstimateInt64> groupCount = null;
      IList<EstimateInt64> defaultHeadersCounts = null;
      IList<EstimateInt64> defaultFootersCounts = null;

      DependentCall totalCountCall = null;
      DependentCall countHeadersCall = null;
      DependentCall countFootersCall = null;
      DependentCall countGroupsCalls = null;
      DependentCall collapsedCountCall = null;


      totalCountCall =
        new DependentCall(
        ( callback, state ) => this.ChildEnumerator.BeginCount(
          parameters,
          callback,
          state ),
          ( parentAr, childAr ) =>
          {
            CountResult countResult = this.ChildEnumerator.EndCount( childAr );

            GroupHandler handler = this.GroupHelper.GroupHandler;
            bool haveCollapseAllState = handler.CollapsedGroupCollections.Any( ( collection ) => collection.IsReversed );
            if( haveCollapseAllState || ( this.AddOrRemoveItems && parameters.StartPosition != null ) )
            {
              return new DependentCallResult( new CountResult( totalCount.ToEstimate() ) );
            }
            if( countResult == null || countResult.Count == null )
            {
              return new DependentCallResult( countResult );
            }

            totalCount = countResult.Count;
            return new DependentCallResult( null, countHeadersCall );

          } );

      countHeadersCall =
        new DependentCall(
        ( callback, state ) => this.BeginHeadersFootersCount(
          this.GroupHandler.GetHeadersEnumerator,
          1,
          callback,
          state ),
          ( parentAr, childAr ) =>
          {
            defaultHeadersCounts = this.EndHeadersFootersCountForDepth( childAr );
            return new DependentCallResult( null, countFootersCall );
          } );

      countFootersCall =
        new DependentCall(
        ( callback, state ) => this.BeginHeadersFootersCount(
          this.GroupHandler.GetFootersEnumerator,
          1,
          callback,
          state ),
          ( parentAr, childAr ) =>
          {
            defaultFootersCounts = this.EndHeadersFootersCountForDepth( childAr );
            return new DependentCallResult( countGroupsCalls );
          } );

      countGroupsCalls =
        new DependentCall(
        ( callback, state ) => this.BeginGroupCount( parameters, callback, state ),
        ( parentAr, childAr ) =>
        {
          GroupCountResult result = this.EndGroupCount( childAr );
          groupCount = (result != null) ? result.GroupCounts : (IList<EstimateInt64>) null;
          return new DependentCallResult( collapsedCountCall );
        } );

      collapsedCountCall =
        new DependentCall(
          ( callback, state ) => this.BeginCollapsedGroupCount( null, false, false, defaultHeadersCounts, defaultFootersCounts, callback, state ),
          ( parentAr, childAr ) =>
          {
            collapsedCount = this.EndCollapsedGroupCount( childAr );

            EstimateInt64 adjustedTotalCount = this.CalculateTotalCount(
              totalCount,
              collapsedCount,
              groupCount,
              defaultHeadersCounts,
              defaultFootersCounts );

            return new DependentCallResult( new CountResult( adjustedTotalCount ) );
          } );


      asyncResult.Start( totalCountCall );

      return asyncResult;
    }

    public override CountResult EndCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( this.CountCallId );
      return asyncResult.GetCallResult<CountResult>();
    }

    private EstimateInt64 CalculateTotalCount(
      EstimateInt64 totalCount,
      EstimateInt64 collapsedCount,
      IList<EstimateInt64> groupCounts,
      IList<EstimateInt64> defaultHeadersCounts,
      IList<EstimateInt64> defaultFootersCounts )
    {
      if( totalCount == null )
        throw new ArgumentNullException( "totalCount" );

      EstimateInt64 adjustedCount = totalCount;

      adjustedCount = this.CalculateGroupCount( DataPath.Empty, totalCount, groupCounts, defaultHeadersCounts, defaultFootersCounts );

      if( collapsedCount != null )
      {
        adjustedCount -= collapsedCount;

        if( adjustedCount.Value < 0 )
        {
          Debug.Assert( adjustedCount.IsEstimate );
          adjustedCount = new EstimateInt64( -adjustedCount.Value, true );
        }
      }
      else
      {
        adjustedCount = adjustedCount.ToEstimate();
      }

      return adjustedCount;
    }

    #endregion Begin/End Count Methods

    #region Begin/End Count management Methods

    private IAsyncResult BeginResolveCountValues(
      int endPositionDirection,
      EstimateInt64 remainingFetchCount,
      DataPath endPositionItem,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount,
      AsyncCallback userCallback,
      object asyncState )
    {

      ContextAsyncResult asyncResult = new ContextAsyncResult( this.ResolveCountCallId, userCallback, asyncState );

      bool needUpdateEndPositionOffset = ( endPositionOffset != null );
      bool needUpdateTotalCount = ( totalCount != null );

      if( !this.AddOrRemoveItems || ( !needUpdateEndPositionOffset && !needUpdateTotalCount ) )
      {
        asyncResult.StartSync( new Tuple<RelativeOffset, EstimateInt64>( endPositionOffset, totalCount ) );
        return asyncResult;
      }

      if( this.GroupCountPolicy == Data.GroupCountPolicy.DoNotHandleCounts )
      {
        asyncResult.StartSync( new Tuple<RelativeOffset, EstimateInt64>( endPositionOffset.ToEstimate(), totalCount.ToEstimate() ) );
        return asyncResult;
      }

      ResolveCountData countData = new ResolveCountData();
      countData.EndPositionOffset = endPositionOffset;
      countData.TotalCount = totalCount;

      asyncResult.CallContext = countData;

      DependentCall countHeadersCall = null;
      DependentCall countFootersCall = null;
      DependentCall unfilteredCollpasedGroupsCountCall = null;
      DependentCall unfilteredGroupsCountCall = null;
      bool filteredCountsEqualUnfilteredCounts = false;
      bool hasHeadersOrFooters = this.GroupHandler.HasHeadersFooters;


      countHeadersCall =
        new DependentCall(
        ( callback, state ) => this.BeginHeadersFootersCount(
          this.GroupHandler.GetHeadersEnumerator,
          1,
          callback,
          state ),
          ( parentAr, childAr ) =>
          {
            countData.DefaultHeadersCounts = this.EndHeadersFootersCountForDepth( childAr );
            return null;
          } );

      countFootersCall =
        new DependentCall(
        ( callback, state ) => this.BeginHeadersFootersCount(
          this.GroupHandler.GetFootersEnumerator,
          1,
          callback,
          state ),
          ( parentAr, childAr ) =>
          {
            countData.DefaultFootersCounts = this.EndHeadersFootersCountForDepth( childAr );
            return null;
          } );

      unfilteredGroupsCountCall =
        new DependentCall(
        ( callback, state ) => this.BeginSubGroupCounts( DataPath.Empty, callback, state ),
        ( parentAr, childAr ) =>
        {
          countData.UnfilteredGroupCounts = this.EndSubGroupCounts( childAr );

          if( filteredCountsEqualUnfilteredCounts )
          {
            countData.FilteredGroupCounts = countData.UnfilteredGroupCounts;
          }

          return null;
        } );


      unfilteredCollpasedGroupsCountCall =
        new DependentCall(
        ( callback, state ) => this.BeginCollapsedGroupCount(
          null,
          false,
          false,
          countData.DefaultHeadersCounts,
          countData.DefaultFootersCounts,
          callback,
          state ),
        ( parentAr, ar ) =>
        {
          countData.UnfilteredCollapsedGroupCount = this.EndCollapsedGroupCount( ar );

          if( filteredCountsEqualUnfilteredCounts )
          {
            countData.FilteredCollapsedGroupCount = countData.UnfilteredCollapsedGroupCount;
          }
          return null;
        } );


      if( hasHeadersOrFooters )
      {
        asyncResult.AddDependentCall( countHeadersCall );
        asyncResult.AddDependentCall( countFootersCall );
      }

      if( needUpdateEndPositionOffset )
      {
        bool fetchAndReferencePositionAreAligned = ( ( endPositionDirection > 0 ) == ( endPositionOffset.ReferencePosition == ReferencePoint.AfterLast ) );

        if( endPositionItem == null )
        {
          if( fetchAndReferencePositionAreAligned )
          {
            countData.FilteredCollapsedGroupCount = 0;
            countData.FilteredGroupCounts = Enumerable
              .Repeat( new EstimateInt64( 0, false ), this.GroupHandler.GroupDescriptions.Count )
              .ToList();
          }
          else
          {
            filteredCountsEqualUnfilteredCounts = true;
          }
        }
        else
        {
          countData.FilteredCollapsedGroupCountIncludeEndPosition = fetchAndReferencePositionAreAligned;
          countData.EndPositionItemCollapsedDepth = this.GroupHandler.GetCollapsedDepth( endPositionItem );

          DependentCall filteredCollapsedCountCall = new DependentCall(
          ( callback, state ) => this.BeginCollapsedGroupCount(
            endPositionItem,
            countData.FilteredCollapsedGroupCountIncludeEndPosition,
            countData.EndPositionOffset.ReferencePosition == ReferencePoint.AfterLast,
            countData.DefaultHeadersCounts,
            countData.DefaultFootersCounts,
            callback,
            state ),
          ( parentAr, ar ) =>
          {
            countData.FilteredCollapsedGroupCount = this.EndCollapsedGroupCount( ar );
            return null;
          } );

          // Get the SortPosition to use for the group count.
          SortPosition groupPosition = this.GetGroupPosition( 
                                         endPositionItem, 
                                         ( countData.EndPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst ) );

          DependentCall filteredGroupCountCall =
          new DependentCall(
          ( callback, state ) => this.BeginGroupCount( new CountParameters( groupPosition ), callback, state ),
          ( parentAr, childAr ) =>
          {
            GroupCountResult groupCount = this.EndGroupCount( childAr );
            countData.FilteredGroupCounts = ( groupCount != null ) ? groupCount.GroupCounts : null;
            return null;
          } );

          if( hasHeadersOrFooters )
          {
            asyncResult.AddDependentCall( filteredGroupCountCall );
          }
          asyncResult.AddDependentCall( filteredCollapsedCountCall );
        }

        countData.EndPositionOffset = DataUtils.CalculateRelativeOffset( countData.EndPositionOffset, -remainingFetchCount.Value, false );

        if( remainingFetchCount.IsEstimate )
        {
          countData.EndPositionOffset = countData.EndPositionOffset.ToEstimate();
        }

      }

      if( needUpdateTotalCount || filteredCountsEqualUnfilteredCounts )
      {
        if( hasHeadersOrFooters )
        {
          asyncResult.AddDependentCall( unfilteredGroupsCountCall );
        }
        asyncResult.AddDependentCall( unfilteredCollpasedGroupsCountCall );
      }

      if( asyncResult.HasDependentCalls )
      {
        asyncResult.Start();
      }
      else
      {
        asyncResult.StartSync( null );
      }

      return asyncResult;
    }


    private Tuple<RelativeOffset, EstimateInt64> EndResolveCountValues( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( this.ResolveCountCallId );
      ContextAsyncResult contextAsyncResult = ( ContextAsyncResult )asyncResult;

      if( contextAsyncResult.CallContext == null )
        return contextAsyncResult.GetCallResult<Tuple<RelativeOffset, EstimateInt64>>();

      ResolveCountData countData = ( ResolveCountData )contextAsyncResult.CallContext;
      RelativeOffset endPositionOffset = countData.EndPositionOffset;
      EstimateInt64 totalCount = countData.TotalCount;

      if( endPositionOffset != null )
      {
        EstimateInt64 offset = endPositionOffset.EstimateValue;

        if( this.GroupHandler.HasHeadersFooters )
        {
          if( countData.FilteredGroupCounts != null )
          {
            IList<EstimateInt64> defaultHeadersCounts = countData.DefaultHeadersCounts;
            IList<EstimateInt64> defaultFootersCounts = countData.DefaultFootersCounts;

            if( endPositionOffset.ReferencePosition == ReferencePoint.AfterLast )
            {
              IList<EstimateInt64> tmpList = defaultHeadersCounts;
              defaultHeadersCounts = defaultFootersCounts;
              defaultFootersCounts = tmpList;
            }

            int collapsedDepth = countData.EndPositionItemCollapsedDepth;
            bool includedInCollapsedCount = countData.FilteredCollapsedGroupCountIncludeEndPosition;

            for( int i = 0; i < countData.FilteredGroupCounts.Count; i++ )
            {
              EstimateInt64 depthCount = countData.FilteredGroupCounts[ i ];
              EstimateInt64 headerCountPerGroup = defaultHeadersCounts[ i ];
              EstimateInt64 footerCountPerGroup = defaultFootersCounts[ i ];

              bool isDepthNotCollapsed = ( collapsedDepth == 0 || i < ( collapsedDepth - 1 ) );

              if( !includedInCollapsedCount || isDepthNotCollapsed )
              {
                if( depthCount.Value > 0 )
                {
                  offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount - 1, footerCountPerGroup ) );
                }
              }
              else
              {
                offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount, footerCountPerGroup ) );
              }

              if( !includedInCollapsedCount && !isDepthNotCollapsed )
              {
                if( depthCount.Value > 0 )
                {
                  offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount - 1, headerCountPerGroup ) );
                }
              }
              else
              {
                offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount, headerCountPerGroup ) );
              }
            }
          }
          else
          {
            offset = offset.ToEstimate();
          }
        }

        if( this.GroupHandler.HasCollapsedGroups )
        {
          if( countData.FilteredCollapsedGroupCount != null )
          {
            offset -= countData.FilteredCollapsedGroupCount;
          }
          else
          {
            offset = offset.ToEstimate();
          }
        }

        endPositionOffset = new RelativeOffset( offset, endPositionOffset.ReferencePosition );
      }

      if( totalCount != null )
      {
        totalCount = this.CalculateTotalCount(
          totalCount,
          countData.UnfilteredCollapsedGroupCount,
          countData.UnfilteredGroupCounts,
          countData.DefaultHeadersCounts,
          countData.DefaultFootersCounts );
      }

      return new Tuple<RelativeOffset, EstimateInt64>( endPositionOffset, totalCount );
    }

    private IAsyncResult BeginHeadersFootersCount(
      Func<DataPath, IAsyncEnumerator> headerOrFooterEnumFactory,
      int depth,
      AsyncCallback userCallback,
      object asyncState )
    {
      AsyncResult asyncResult = new AsyncResult( this.HeadersFootersCountCallId, userCallback, asyncState );

      DataPath groupPath = new DataPath( Enumerable.Repeat( GroupDataItem.Any, depth ).ToArray() );

      IAsyncEnumerator headersOrFootersEnumerator = headerOrFooterEnumFactory( groupPath );

      List<EstimateInt64> result = new List<EstimateInt64>();

      asyncResult.AddDependentCall( ( callback, state ) => headersOrFootersEnumerator.BeginCount( new CountParameters(), callback, state ),
        ( parentAr, childAr ) =>
        {
          CountResult enumResult = headersOrFootersEnumerator.EndCount( childAr );
          result.Add( (enumResult != null) ? enumResult.Count : null );
          return new DependentCallResult( result );
        } );

      if( depth < this.GroupHandler.GroupDescriptions.Count )
      {
        asyncResult.AddDependentCall( ( callback, state ) => this.BeginHeadersFootersCount( headerOrFooterEnumFactory, depth + 1, callback, state ),
          ( parentAr, childAr ) =>
          {
            result.AddRange( this.EndHeadersFootersCountForDepth( childAr ) );
            return new DependentCallResult( result );
          } );
      }

      asyncResult.Start();

      return asyncResult;
    }

    private List<EstimateInt64> EndHeadersFootersCountForDepth( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( this.HeadersFootersCountCallId );
      return asyncResult.GetCallResult<List<EstimateInt64>>();
    }

    private IAsyncResult BeginCollapsedGroupCount(
      DataPath limit,
      bool isLimitIncluded,
      bool isLowerLimit,
      IList<EstimateInt64> defaultHeadersCount,
      IList<EstimateInt64> defaultFootersCount,
      AsyncCallback userCallback,
      object asyncState )
    {
      int groupLevelCount = this.GroupHandler.GroupDescriptions.Count;

      if( defaultHeadersCount != null && defaultHeadersCount.Count != groupLevelCount )
        throw new ArgumentException( "The specified defaultHeadersCount must be equal to the size of the GroupDescriptions collection.", "defaultHeadersCount" );

      if( defaultFootersCount != null && defaultFootersCount.Count != groupLevelCount )
        throw new ArgumentException( "The specified defaultFootersCount must be equal to the size of the GroupDescriptions collection.", "defaultFootersCount" );

      AsyncResult asyncResult = new AsyncResult( this.RangeCountCallId, userCallback, asyncState );

      EstimateInt64 collapsedCountDiff = 0;
      IEnumerator<Tuple<GroupState, DataPath>> collapsedGroups =
        this.GroupHelper.GroupHandler.CollapsedGroupCollections.IncludedGroups
        .ToList().GetEnumerator();

      Func<EstimateInt64, DependentCallResult> getNextCall = null;
      DependentCall subCountCall = null;
      DependentCall subGroupCountsCall = null;
      bool hasHeadersOfFooters = this.GroupHandler.HasHeadersFooters;

      subCountCall =
        new DependentCall(
          ( callback, state ) => this.BeginGroupItemCount(
            collapsedGroups.Current.Item2,
            callback,
            state ),
          ( parentAr, childAr ) =>
          {
            EstimateInt64 currentGroupCount = this.EndGroupItemCount( childAr );

            return ( !hasHeadersOfFooters || currentGroupCount == null || currentGroupCount.Value == 0 )
              ? getNextCall( currentGroupCount )
              : new DependentCallResult( currentGroupCount, subGroupCountsCall );
          } );

      subGroupCountsCall =
        new DependentCall(
          ( callback, state ) => this.BeginSubGroupCounts(
            collapsedGroups.Current.Item2,
            callback,
            state ),
          ( parentAr, childAr ) =>
          {
            EstimateInt64 count = ( EstimateInt64 )childAr.AsyncState;
            IList<EstimateInt64> groupCounts = this.EndSubGroupCounts( childAr );

            EstimateInt64 currentGroupCount = this.CalculateGroupCount(
              collapsedGroups.Current.Item2,
              count,
              groupCounts,
              defaultHeadersCount,
              defaultFootersCount );

            return getNextCall( currentGroupCount );
          } );

      getNextCall = ( currentGroupCount ) =>
      {
        if( currentGroupCount == null )
        {
          collapsedCountDiff = collapsedCountDiff.ToEstimate();
        }
        else
        {
          if( currentGroupCount.Value != 0 )
          {
            currentGroupCount -= 1;
          }
          collapsedCountDiff += currentGroupCount;
        }

        DependentCall nextCall = null;
        while( nextCall == null && collapsedGroups.MoveNext() )
        {
          Debug.Assert( collapsedGroups.Current.Item1 == GroupState.Collapsed );

          if( collapsedGroups.Current.Item1 == GroupState.Expanded )
          {
            Debug.Assert( false, "Collapse all state not supported yet." );
            collapsedCountDiff = collapsedCountDiff.ToEstimate();
            continue;
          }

          DataPath collapsedGroup = collapsedGroups.Current.Item2;

          int collapsedDepth = this.GroupHelper.GroupHandler.GetCollapsedDepth( collapsedGroup );

          if( collapsedDepth < collapsedGroup.Depth )
          {
            Debug.Assert( collapsedDepth != 0, "This is supposed to be a collapsed group." );
            continue;
          }

          if( limit != null )
          {
            bool isEqual = false;
            Func<bool> compareLimit =
              ( isLowerLimit )
              ? ( Func<bool> )( () => this.GroupHelper.Compare( limit, collapsedGroup ) > 0 )
              : ( Func<bool> )( () => this.GroupHelper.Compare( limit, collapsedGroup ) < 0 );

            isEqual = collapsedGroup.IsAncestorOf( limit )
              || collapsedGroup.IsDescendantOf( limit )
              || collapsedGroup.Equals( limit );

            if( ( isEqual && !isLimitIncluded )
              || ( !isEqual && compareLimit() ) )
              continue;
          }

          nextCall = subCountCall;
        }

        return new DependentCallResult( collapsedCountDiff, nextCall );
      };

      DependentCallResult firstResult = getNextCall( 0 );
      DependentCall firstCall = firstResult.NextCall;

      if( firstCall != null )
      {
        asyncResult.Start( firstCall.AsyncCall, firstCall.IntermediateCallback );
      }
      else
      {
        asyncResult.StartSync( firstResult.ResultData );
      }

      return asyncResult;
    }

    private EstimateInt64 EndCollapsedGroupCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( this.RangeCountCallId );
      return asyncResult.GetCallResult<EstimateInt64>();
    }

    public IAsyncResult BeginGroupItemCount(
      DataPath groupPath,
      AsyncCallback userCallback,
      object asyncState )
    {
      return this.BeginSubCall(
        this.SubCountCallId,
        ( param, callback, state ) => this.ChildEnumerator.BeginCount( param, callback, state ),
        ( ar ) => 
          {
            CountResult countResult = this.ChildEnumerator.EndCount( ar );
            return ( countResult != null ) ? countResult.Count : null;
          },
        this.TryGetSubCount,
        groupPath,
        userCallback,
        asyncState );
    }

    public EstimateInt64 EndGroupItemCount( IAsyncResult asyncResult )
    {
      return ( EstimateInt64 )this.EndSubCall( asyncResult, this.SubCountCallId, m_subCountCache );
    }

    private IAsyncResult BeginSubGroupCounts(
      DataPath groupPath,
      AsyncCallback userCallback,
      object asyncState )
    {
      return this.BeginSubCall<IList<EstimateInt64>>(
        this.SubGroupCountsCallId,
        ( param, callback, state ) => this.BeginGroupCount( param, callback, state ),
        ( ar ) => 
          {
            GroupCountResult result = this.EndGroupCount( ar );
            return (result != null) ? result.GroupCounts : null;
          },
        this.TryGetSubGroupCounts,
        groupPath,
        userCallback,
        asyncState );
    }

    private IList<EstimateInt64> EndSubGroupCounts( IAsyncResult asyncResult )
    {
      return ( IList<EstimateInt64> )this.EndSubCall( asyncResult, this.SubGroupCountsCallId, m_subGroupCountsCache );
    }

    private IAsyncResult BeginSubCall<T>(
      Guid callId,
      Func<CountParameters, AsyncCallback, object, IAsyncResult> beginCall,
      Func<IAsyncResult, object> endCall,
      Func<DataPath, T> cacheAccess,
      DataPath groupPath,
      AsyncCallback userCallback,
      object asyncState )
    {

      ContextAsyncResult asyncResult = new ContextAsyncResult( callId, userCallback, asyncState );
      asyncResult.CallContext = groupPath;

      T cachedValue = cacheAccess( groupPath );

      if( cachedValue != null )
      {
        asyncResult.StartSync( cachedValue );
      }
      else
      {
        SortPosition groupPosition = this.GetGroupPosition( groupPath, false );
        CountParameters countParameters = new CountParameters( groupPosition, true );

        asyncResult.Start(
            ( callback, state ) => beginCall( countParameters, callback, state ),
            ( parentAr, childAr ) => new DependentCallResult( endCall( childAr ) ) );
      }

      return asyncResult;
    }

    private object EndSubCall( IAsyncResult asyncResult, Guid callId, IDictionary cache )
    {
      asyncResult.ValidateCallEnd( callId );

      DataPath groupPath = asyncResult.GetCallContext<DataPath>();

      object result = asyncResult.GetCallResult<object>();

      if( result != null )
      {
        if( this.GroupHelper.GroupHandler.GroupCountPolicy == GroupCountPolicy.Cache
          || this.GroupHelper.GroupHandler.GroupCountPolicy == GroupCountPolicy.CacheAsEstimates )
        {
          if( cache.Contains( groupPath ) )
          {
            cache[ groupPath ] = result;
          }
          else
          {
            cache.Add( groupPath, result );
          }
        }
      }

      return result;
    }

    private EstimateInt64 CalculateGroupCount(
      DataPath groupPath,
      EstimateInt64 basicCount,
      IList<EstimateInt64> subLevelGroupCount,
      IList<EstimateInt64> defaultHeadersCount,
      IList<EstimateInt64> defaultFootersCount )
    {
      if( basicCount == null )
        throw new ArgumentNullException( "basicCount" );

      if( basicCount.Value > 0 )
      {
        int groupDepth = Math.Max( groupPath.Depth, 1 );

        if( this.GroupHandler.HasHeadersFooters )
        {
          if( defaultHeadersCount != null || defaultFootersCount != null )
          {
            Action<int, IList<EstimateInt64>> considerHeaderFooters =
              ( index, headersFootersCounts ) =>
              {
                EstimateInt64 headersFootersCount = null;
                EstimateInt64 groupsCount = null;

                if( headersFootersCounts != null )
                {
                  headersFootersCount = headersFootersCounts[ index - 1 ];
                }

                if( subLevelGroupCount != null )
                {
                  groupsCount = subLevelGroupCount[ index - 1 ];
                }

                EstimateInt64 levelHeadersFootersCount = this.MultiplyEstimates( headersFootersCount, groupsCount );

                basicCount = this.AddEstimates( basicCount, levelHeadersFootersCount );
              };

            for( int i = groupDepth; i <= this.GroupHandler.GroupDescriptions.Count; i++ )
            {
              considerHeaderFooters( i, defaultHeadersCount );
              considerHeaderFooters( i, defaultFootersCount );
            }
          }

          if( defaultFootersCount == null || defaultFootersCount == null )
          {
            basicCount = basicCount.ToEstimate();
          }
        }
      }

      return basicCount;
    }

    private EstimateInt64 AddEstimates( EstimateInt64 first, EstimateInt64 second )
    {
      return this.EstimatesOperation( first, second, ( a, b ) => ( a + b ) );
    }

    private EstimateInt64 MultiplyEstimates( EstimateInt64 first, EstimateInt64 second )
    {
      return this.EstimatesOperation( first, second, ( a, b ) => ( a * b ) );
    }

    private EstimateInt64 EstimatesOperation( EstimateInt64 first, EstimateInt64 second, Func<long, long, long> operation )
    {
      if( first == null && second == null )
        return null;

      EstimateInt64 result = null;

      if( first == null )
      {
        result = second.ToEstimate();
      }
      else if( second == null )
      {
        result = first.ToEstimate();
      }
      else
      {
        result = new EstimateInt64( operation( first.Value, second.Value ), first.IsEstimate || second.IsEstimate );
      }

      return result;
    }

    private FilterExpressionBase CreateFilterForGroup( DataPath groupPath )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      if( groupPath.Depth == 0 )
        return null;

      List<FilterExpressionBase> groupFilters = new List<FilterExpressionBase>();
      for( int i = 0; i < groupPath.Depth; i++ )
      {
        Debug.Assert( i < this.GroupHelper.GroupHandler.GroupDescriptions.Count );

        GroupDescription group = this.GroupHelper.GroupHandler.GroupDescriptions[ i ];
        DataItem item = groupPath[ i ];

        Debug.Assert( item is GroupDataItem );

        groupFilters.Add( new FilterExpression( group.GetPropertyName(), FilterOperator.Equal, item.Data ) );
      }

      if( groupFilters.Count == 1 )
      {
        return groupFilters[ 0 ];
      }
      else
      {
        Debug.Assert( groupFilters.Count != 0 );
        return new AndFilterExpression( groupFilters );
      }
    }

    private FilterExpressionBase CreateFilterForItem( DataPath itemPath, bool lowerLimit, bool includeLimit )
    {
      SortDescription[] completeSort = this.GroupHelper.CompleteSort;
      List<object> sortValues = this.GroupHelper.ExtractSortValues( completeSort, itemPath ).ToList();

      return NavigationFilterUtils.CreateFilterExpression(
        completeSort.Take( sortValues.Count ).ToList(),
        sortValues,
        !lowerLimit,
        includeLimit );
    }

    private IList<EstimateInt64> TryGetSubGroupCounts( DataPath groupPath )
    {
      Func<object, object> toEstimate = ( counts ) =>
        ( ( IList<EstimateInt64> )counts ).Select( ( value ) => value.ToEstimate() ).ToList();

      IList<EstimateInt64> result =
      ( IList<EstimateInt64> )
        this.TryGetCachedCountValue( m_subGroupCountsCache, toEstimate, groupPath );

      return result;
    }

    private EstimateInt64 TryGetSubCount( DataPath groupPath )
    {
      Func<object, object> toEstimate = ( count ) => ( ( EstimateInt64 )count ).ToEstimate();

      return ( EstimateInt64 )
        this.TryGetCachedCountValue( m_subCountCache, toEstimate, groupPath );

    }

    private object TryGetCachedCountValue( IDictionary cache, Func<object, object> toEstimate, DataPath groupPath )
    {
      if( cache == null )
        throw new ArgumentNullException( "cache" );

      object cachedValue = null;

      if( this.GroupCountPolicy == GroupCountPolicy.Cache
        || this.GroupCountPolicy == GroupCountPolicy.CacheAsEstimates )
      {
        if( cache.Contains( groupPath ) )
        {
          cachedValue = cache[ groupPath ];

          if( this.GroupCountPolicy == Data.GroupCountPolicy.CacheAsEstimates )
          {
            cachedValue = toEstimate( cachedValue );
          }
        }
      }

      return cachedValue;
    }

    #endregion Begin/End Count management Methods

    #region Change Handling management Methods

    protected override void ChildDataChanged( DataChangedEventArgs e )
    {
      if( this.GroupHelper.GroupHandler.GroupDescriptions.Count == 0 )
      {
        this.RaiseDataChanged( e );
        return;
      }

      DataPath newPath = ( e.NewItem != null ) ? this.ConvertToGroupedDataPath( ( DataPath )e.NewItem ) : null;
      DataPath oldPath = ( e.OldItem != null ) ? this.ConvertToGroupedDataPath( ( DataPath )e.OldItem ) : null;
      DataPath itemChangedTarget = ( e.ItemChangedInfo != null )
        ? this.ConvertToGroupedDataPath( ( DataPath )e.ItemChangedInfo.Target )
        : null;

      bool handled;

      if( e.Action == DataChangedAction.Reset )
      {
        m_subCountCache.Clear();
        handled = false;
      }
      else if( e.Action == DataChangedAction.ItemChanged )
      {
        //This is a property changed. Handle it.
        //For the moment, no property value or old value are available.
        handled = this.HandleItemChanged( e.ItemChangedInfo.CreateWithNewTarget( itemChangedTarget ) );
      }
      else
      {
        this.UpdateCounts( newPath, oldPath );

        handled = this.HandleChangeEvent( oldPath, newPath, false );
        Debug.Assert( handled );
      }

      if( !handled )
      {
        ItemChangedInfo convertedInfo = ( e.ItemChangedInfo != null )
          ? e.ItemChangedInfo.CreateWithNewTarget( itemChangedTarget )
          : null;

        this.RaiseDataChanged( new DataChangedEventArgs( e.Action, newPath, oldPath, e.RemovePosition, e.NewCurrentPosition, convertedInfo ) );
      }

    }


    private void UpdateCounts( DataPath newPath, DataPath oldPath )
    {
      this.UpdateSubCounts( newPath, oldPath );
      this.UpdateSubGroupCounts( newPath, oldPath );
    }

    private void UpdateSubCounts( DataPath newPath, DataPath oldPath )
    {
      foreach( DataPath path in new List<DataPath>( m_subCountCache.Keys ) )
      {

        bool haveUnknownValues = ( oldPath != null && oldPath.Depth < path.Depth );
        bool pathReferNewValue;
        bool pathMayReferOldValue;

        this.GetReferedPaths( path, newPath, oldPath, out pathReferNewValue, out pathMayReferOldValue );

        if( pathReferNewValue )
        {
          m_subCountCache[ path ] = m_subCountCache[ path ] + 1;
        }

        if( pathMayReferOldValue )
        {
          if( haveUnknownValues )
          {
            m_subCountCache.Remove( path );
          }
          else
          {
            EstimateInt64 newCount = m_subCountCache[ path ] - 1;

            if( newCount.Value <= 0 )
            {
              m_subCountCache.Remove( path );
            }
            else
            {
              m_subCountCache[ path ] = newCount;
            }
          }
        }
      }
    }

    private void UpdateSubGroupCounts( DataPath newPath, DataPath oldPath )
    {
      foreach( DataPath path in new List<DataPath>( m_subGroupCountsCache.Keys ) )
      {
        bool pathReferNewValue;
        bool pathMayReferOldValue;

        this.GetReferedPaths( path, newPath, oldPath, out pathReferNewValue, out pathMayReferOldValue );

        if( pathReferNewValue || pathMayReferOldValue )
        {
          m_subGroupCountsCache.Remove( path );
        }
      }
    }

    private void GetReferedPaths( 
      DataPath path, 
      DataPath newPath, 
      DataPath oldPath, 
      out bool pathReferNewPath,
      out bool pathMayReferOldPath )
    {
      IList<GroupDescription> grouping = this.GroupHelper.GroupHandler.GroupDescriptions;

      pathReferNewPath = ( newPath != null ) ? true : false;
      pathMayReferOldPath = ( oldPath != null ) ? true : false;

      bool haveUnknownValues = ( oldPath != null && oldPath.Depth < path.Depth );

      for( int i = 0; i < path.Depth; i++ )
      {
        object pathValue = path[ i ].Data;

        if( pathReferNewPath )
        {
          object newValue = m_groupHelper.GetMemberValue( newPath, grouping[ i ] );
          pathReferNewPath = object.Equals( newValue, pathValue );
        }

        if( pathMayReferOldPath && i < oldPath.Depth )
        {
          object oldValue = m_groupHelper.GetMemberValue( oldPath, grouping[ i ] );
          pathMayReferOldPath = object.Equals( oldValue, pathValue );
        }
      }
    }

    private bool HandleItemChanged( ItemChangedInfo itemChanged )
    {
      if( itemChanged == null )
        throw new ArgumentNullException( "itemChanged" );

      DataPath newCompletePath = ( DataPath )itemChanged.Target;

      if( newCompletePath.Depth <= this.GroupHelper.GroupHandler.GroupDescriptions.Count )
        throw new DataInternalException();

      DataPath cachedPath = null;

      if( this.CachedPathProvider != null )
      {
        cachedPath = this.CachedPathProvider( newCompletePath );

        if( cachedPath != null && cachedPath.Depth != newCompletePath.Depth )
          throw new DataInternalException();
      }

      DataPath longestAvailableOldPath =
        m_groupHelper.ReCreateOldPath( itemChanged, cachedPath, newCompletePath );

      int groupCount = this.GroupHelper.GroupHandler.CollapsedGroupCollections.Count;

      if( longestAvailableOldPath.Depth >= groupCount
        && DataPath.AreSameItems( longestAvailableOldPath.Path, newCompletePath.Path, groupCount ) )
      {
        return false;
      }

      //Update the group counts
      this.UpdateCounts( newCompletePath, longestAvailableOldPath );

      int oldCollapsedDepth = this.GroupHelper.GroupHandler.GetCollapsedDepth( longestAvailableOldPath );
      int newCollapsedDepth = this.GroupHelper.GroupHandler.GetCollapsedDepth( newCompletePath );

      if( ( oldCollapsedDepth != 0 )
        && ( oldCollapsedDepth == newCollapsedDepth )
        && ( DataPath.AreSameItems( longestAvailableOldPath.Path, newCompletePath.Path, oldCollapsedDepth ) ) )
      {
        //The modifications are under a collapsed group. No impact.
        //Stop the event chain.
        return true;
      }

      return this.HandleChangeEvent( longestAvailableOldPath, newCompletePath, longestAvailableOldPath.Depth != newCompletePath.Depth );
    }


    private bool HandleChangeEvent( DataPath oldItem, DataPath newItem, bool oldPathIsIncomplete )
    {
      DataPath addedPath = null;
      DataPath removedPath = null;

      DataPath newCollapsedPath = null;
      DataPath oldCollapsedPath = null;

      long oldCollapsedCount = -1;
      long newCollapsedCount = -1;

      if( oldItem != null )
      {
        this.TryGetCollapsedPathWithRealCount( oldItem, out oldCollapsedPath, out oldCollapsedCount );
      }

      if( newItem != null )
      {
        this.TryGetCollapsedPathWithRealCount( newItem, out newCollapsedPath, out newCollapsedCount );

        Debug.Assert( newCollapsedCount != 0, "How can we have an empty group for the new value" );
      }

      //If it is the same path than the old one, it is not added, it replace the old one....
      if( ( newCollapsedCount == 1 ) && !object.Equals( newCollapsedPath, oldCollapsedPath ) )
      {
        addedPath = newCollapsedPath;
      }
      else if( newCollapsedCount == -1 )
      {
        addedPath = newItem;
      }

      if( oldCollapsedCount == 0 )
      {
        removedPath = oldCollapsedPath;
      }
      else if( oldCollapsedCount == -1 )
      {
        removedPath = oldItem;
      }

      if( removedPath != null && object.ReferenceEquals( oldItem, removedPath ) && oldPathIsIncomplete )
      {
        return false;
      }
      else
      {
        if( addedPath != null && removedPath != null )
        {
          this.RaiseDataReplaced( removedPath, addedPath );
        }
        else if( addedPath != null )
        {
          this.RaiseDataAdd( addedPath );
        }
        else if( removedPath != null )
        {
          this.RaiseDataRemoved( removedPath, null, null );
        }

        return true;
      }
    }

    private bool TryGetCollapsedPathWithRealCount( DataPath dataPath, out DataPath collapsedPath, out long collapsedCount )
    {
      collapsedPath = null;
      collapsedCount = -1;
      int collapsedDepth = this.GroupHelper.GroupHandler.GetCollapsedDepth( dataPath );

      if( collapsedDepth > 0 )
      {
        //If the item is the only one in the collapsed group, it result in a group add
        collapsedPath = ( collapsedDepth < dataPath.Depth )
          ? dataPath.CreateAncestorPath( collapsedDepth )
          : dataPath;

        EstimateInt64 collapsedGroupCount = this.TryGetSubCount( collapsedPath );

        if( collapsedGroupCount == null
          && this.GroupCountPolicy == GroupCountPolicy.Query
          && this.IsFullySynchronized )
        {
          collapsedGroupCount = this.EndGroupItemCount( this.BeginGroupItemCount( collapsedPath, null, null ) );
        }

        if( collapsedGroupCount != null
          && !collapsedGroupCount.IsEstimate )
        {
          collapsedCount = collapsedGroupCount.Value;
          Debug.Assert( collapsedCount >= 0 );
        }
      }

      return ( collapsedPath != null );
    }

    private DataPath ConvertToGroupedDataPath( DataPath path )
    {
      return ( DataPath )this.GroupHelper.ConvertLocal( path );
    }

    #endregion Change Handling management Methods

    private DataPath GetHeaderPath( DataPath path )
    {
      DataPath headerPath = null;

      //Handle any header/footer of this depth.
      for( int i = 0; i < path.Depth; i++ )
      {
        if( path[ i ] is HeaderFooterDataItem )
        {
          headerPath = path.CreateAncestorOrSamePath( i + 1 );
          break;
        }
      }

      return headerPath;
    }

    private void ExtractFetchSourcePath(
      JumpMoveAndFetchParameters parameters,
      out DataPath completeHeaderPath,
      out DataPath startingPath,
      out Nullable<StartPosition> startPosition )
    {
      SourcePosition sourcePosition = parameters.SourcePosition;
      startingPath = null;
      startPosition = null;
      completeHeaderPath = null;

      if( sourcePosition is ItemPosition )
      {
        startingPath = ( DataPath )( ( ItemPosition )sourcePosition ).Item;
        DataPath headerPath = this.GetHeaderPath( startingPath );

        if( headerPath != null )
        {
          HeaderFooterDataItem headerItem = ( HeaderFooterDataItem )headerPath.LastChild;

          //Header is a group concept. exclude it an fetch the underling group.
          completeHeaderPath = startingPath;
          startingPath = headerPath.ParentPath;

          //A footer when moving forward, or a header when moving backward must be excluded
          if( parameters.ExpectedMoveCount != 0 )
          {
            completeHeaderPath = null;
            if( ( parameters.ExpectedMoveCount < 0 ) == headerItem.IsHeader )
            {
              startPosition = ( parameters.ExpectedMoveCount > 0 )
                ? StartPosition.After
                : StartPosition.Before;
            }
          }
        }

        if( startPosition == null
          && startingPath.Depth <= this.GroupHandler.GroupDescriptions.Count )
        {
          //If the item refer to a group, it must be converted to a SortPosition.
          //Include the item in the fetch.
          startPosition = DataUtils.IsQueryingForward( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount )
            ? StartPosition.Before
            : StartPosition.After;
        }
      }
      else if( sourcePosition is SortPosition )
      {
        this.GroupHelper.CreatePathFromSortPosition( ( SortPosition )sourcePosition, out startingPath, out startPosition );
      }

      if( startingPath != null && parameters.ExpectedMoveCount != 0 )
      {
        int collapsedDepth = this.GroupHandler.GetCollapsedDepth( startingPath );

        if( collapsedDepth > 0 )
        {
          completeHeaderPath = null;
          startingPath = startingPath.CreateAncestorOrSamePath( collapsedDepth );
          startPosition = ( parameters.ExpectedMoveCount > 0 )
            ? StartPosition.After
            : StartPosition.Before;
        }
      }
    }

    protected JumpMoveAndFetchParameters OptimizeParameters( JumpMoveAndFetchParameters parameters )
    {
      DataPath startingPath = null;
      Nullable<StartPosition> startPosition = null;
      DataPath completeHeaderPath = null;

      this.ExtractFetchSourcePath( parameters, out completeHeaderPath, out startingPath, out startPosition );

      if( startPosition != null )
      {
        SourcePosition startingSourcePosition =
          new SortPosition(
            this.GroupHelper.CompleteSort,
            startingPath.Path.Select( ( item ) => item.Data ).ToList(),
            startPosition.Value );

        parameters = parameters.CreateWithNewSourcePosition( startingSourcePosition );
      }

      return parameters;
    }

    private DependentCallResult CompleteOperation(
      GroupAsyncResult groupAr,
      ChildResultEnumerator childResultEnumerator )
    {
      if( childResultEnumerator.HasCurrent && groupAr.RemainingFetchCount != 0 )
        throw new DataInternalException();

      DataPath endPositionItem;
      RelativeOffset endPositionOffset = childResultEnumerator.CurrentItemPositionOffset;
      EstimateInt64 remainingFetchCount;
      EstimateInt64 totalCount = childResultEnumerator.ChildResult.TotalCount;
      bool result;

      long? actualMoveCount;
      ICollection<DataPath> fetchedItems;
      SourcePosition endPosition;

      Debug.Assert( groupAr.ExpectedFetchCount != 0 );

      if( !childResultEnumerator.HasCurrent )
      {
        //End of source reached.
        endPositionItem = null;
      }
      else
      {
        endPositionItem = childResultEnumerator.Current;
      }

      if( groupAr.CumulativeFetch.Count > 0 )
      {
        actualMoveCount = groupAr.ExpectedMoveCount;
        fetchedItems = groupAr.CumulativeFetch;
      }
      else
      {
        //Failed move.
        Debug.Assert( !childResultEnumerator.HasCurrent );
        actualMoveCount = childResultEnumerator.ChildResult.ActualMoveCount;
        fetchedItems = null;
      }

      if( groupAr.RemainingFetchCount == 0 )
      {
        result = true;
        endPosition = new ItemPosition( groupAr.CumulativeFetch.Last() );
        remainingFetchCount = groupAr.CumulativeFetchExtent - groupAr.CumulativeFetchCount;

        if( endPositionItem == null )
        {
          remainingFetchCount += groupAr.GetFetchUnit();
        }
      }
      else
      {
        Debug.Assert( groupAr.CumulativeFetchExtent == groupAr.CumulativeFetchCount );
        Debug.Assert( !childResultEnumerator.HasCurrent );
        result = false;
        endPosition = childResultEnumerator.ChildResult.EndPositionObject;
        remainingFetchCount = 0;
      }

      int endPositionDirection;

      if( fetchedItems != null )
      {
        endPositionDirection = groupAr.GetFetchUnit();
      }
      else if( groupAr.SourcePosition.IsOutOfBound && groupAr.ExpectedMoveCount == 0 )
      {
        endPositionDirection = ( groupAr.SourcePosition.IsBeforeFirst )
          ? -1
          : 1;
      }
      else if( groupAr.ExpectedMoveCount != 0 )
      {
        endPositionDirection = groupAr.GetMoveUnit();
      }
      else
      {
        endPositionDirection = groupAr.GetFetchUnit();
      }


      return
        new DependentCallResult( null,
        ( callback, state ) => this.BeginResolveCountValues(
          endPositionDirection,
          remainingFetchCount,
          endPositionItem,
          endPositionOffset,
          totalCount,
          callback,
          state ),
        ( parentAr, childAr ) =>
        {
          Tuple<RelativeOffset, EstimateInt64> countResult = this.EndResolveCountValues( childAr );

          return new DependentCallResult(
            new JumpMoveAndFetchResult<DataPath>(
              groupAr,
              actualMoveCount,
              fetchedItems,
              result,
              endPosition,
              countResult.Item1,
              countResult.Item2 ) );
        } );
    }

    private SortPosition GetGroupPosition( DataPath dataPath, bool reverse )
    {
      IEnumerable<SortDescription> sortOrder = m_groupHelper.CompleteSort;
      IEnumerable<string> groupedPropertyName = m_groupHelper.GroupHandler.PropertyNames;
      IEnumerable<SortDescription> groupSortOrder = sortOrder.Where( ( sort ) => groupedPropertyName.Contains( sort.PropertyName ) );

      // Reverse the sort order if desired.
      if( reverse )
      {
        groupSortOrder = ( from sort in groupSortOrder
                           select new SortDescription(
                                    sort.PropertyName,
                                    ( sort.Direction == ListSortDirection.Ascending )
                                      ? ListSortDirection.Descending
                                      : ListSortDirection.Ascending ) );
      }

      IEnumerable<object> groupSortValues = m_groupHelper.ExtractSortValues( groupSortOrder, dataPath );

      return new SortPosition( groupSortOrder.ToList(), groupSortValues.ToList(), StartPosition.Before );
    }


    #region Private Fields

    private GroupHelper m_groupHelper;//null
    private Dictionary<DataPath, EstimateInt64> m_subCountCache = new Dictionary<DataPath, EstimateInt64>();
    private Dictionary<DataPath, IList<EstimateInt64>> m_subGroupCountsCache = new Dictionary<DataPath, IList<EstimateInt64>>();

    #endregion


    #region GroupAsyncResult class

    private class GroupAsyncResult : JumpMoveAndFetchAsyncResult
    {
      public GroupAsyncResult( Guid callId, AsyncCallback asyncCallback, object asyncState, JumpMoveAndFetchParameters parameters )
        : base( callId, asyncCallback, asyncState, parameters )
      {
        this.CumulativeFetch = new List<DataPath>();
        this.CumulativeFetchExtent = 0;
      }


      public long CumulativeFetchCount
      {
        get
        {
          return this.CumulativeFetch.Count * this.GetFetchUnit();
        }
      }

      public EstimateInt64 CumulativeFetchExtent
      {
        get;
        private set;
      }

      public List<DataPath> CumulativeFetch
      {
        get;
        private set;
      }

      public int RemainingFetchCount
      {
        get
        {
          return this.ExpectedFetchCount - ( this.CumulativeFetch.Count * this.GetFetchUnit() );
        }
      }

      public void AddFetchResult( SimpleFetchResult fetchResult )
      {
        this.CumulativeFetch.AddRange( fetchResult.FetchedItems );

        if( this.CumulativeFetch.Count > Math.Abs( this.ExpectedFetchCount ) )
          throw new DataInternalException();

        if( fetchResult.InlineCount != null )
        {
          this.CumulativeFetchExtent = new EstimateInt64(
            this.CumulativeFetchExtent.Value + ( fetchResult.InlineCount.Value * this.GetFetchUnit() ),
            this.CumulativeFetchExtent.IsEstimate || this.CumulativeFetchExtent.IsEstimate );
        }
        else
        {
          this.CumulativeFetchExtent = this.CumulativeFetchExtent.ToEstimate();
        }
      }
    }
    #endregion GroupAsyncResult class

    #region ChildItemEnumerator class

    private class ChildResultEnumerator
    {
      public ChildResultEnumerator( JumpMoveAndFetchResult<DataPath> childResult )
      {
        if( childResult == null )
          throw new ArgumentNullException( "childResult" );

        m_childResult = childResult;
        if( m_childResult.FetchedItems != null )
        {
          m_childEnumerator = m_childResult.FetchedItems.GetEnumerator();
          this.HasCurrent = m_childEnumerator.MoveNext();
        }
        else
        {
          m_childEnumerator = null;
          this.HasCurrent = false;
        }
      }

      public JumpMoveAndFetchResult<DataPath> ChildResult
      {
        get
        {
          return m_childResult;
        }
      }

      public bool HasCurrent
      {
        get;
        private set;
      }

      public DataPath Current
      {
        get
        {
          return ( this.HasCurrent )
            ? m_childEnumerator.Current
            : null;
        }
      }

      public RelativeOffset CurrentItemPositionOffset
      {
        get
        {
          if( m_childResult == null || m_childResult.EndPositionOffset == null )
            return null;

          if( !this.HasCurrent )
            return m_childResult.EndPositionOffset;

          int index = -1;
          foreach( DataPath item in m_childResult.FetchedItems )
          {
            index++;
            if( object.Equals( item, this.Current ) )
              break;
          }

          Debug.Assert( index != -1 );

          int endOffset = ( m_childResult.FetchedItems.Count - 1 ) - index;

          if( !m_childResult.Result )
            endOffset++;

          return DataUtils.CalculateRelativeOffset(
            m_childResult.EndPositionOffset,
            -( endOffset * m_childResult.GetFetchUnit() ) );
        }
      }

      public bool MoveNext()
      {
        if( this.HasCurrent )
        {
          this.HasCurrent = m_childEnumerator.MoveNext();
        }

        return this.HasCurrent;
      }

      private JumpMoveAndFetchResult<DataPath> m_childResult; //null
      private IEnumerator<DataPath> m_childEnumerator; //null
    }

    #endregion ChildItemEnumerator class

    #region SimpleFetchResult class

    private class SimpleFetchResult
    {
      private static readonly ICollection<DataPath> EmptyCollection = new ReadOnlyCollection<DataPath>( new List<DataPath>() );

      public SimpleFetchResult()
      {
        m_result = new List<DataPath>();
        m_resultReadOnly = new ReadOnlyCollection<DataPath>( m_result );
        m_count = 0;
      }

      public SimpleFetchResult( IEnumerable<DataPath> items, EstimateInt64 count )
      {
        if( items == null )
          throw new ArgumentNullException( "items" );

        m_result = items.ToList();
        m_resultReadOnly = new ReadOnlyCollection<DataPath>( m_result );
        m_count = count;
      }

      public void Add( SimpleFetchResult otherResult )
      {
        this.Add( otherResult.m_result, otherResult.m_count );
      }

      public void Add( IEnumerable<DataPath> items, EstimateInt64 count )
      {
        if( items == null )
          throw new ArgumentNullException( "items" );

        m_result.AddRange( items );
        if( m_count == null )
        {
          m_count = count.ToEstimate();
        }
        else if( count == null )
        {
          m_count = m_count.ToEstimate();
        }
        else
        {
          m_count += count;
        }
      }

      public ICollection<DataPath> FetchedItems
      {
        get
        {
          return m_resultReadOnly;
        }
      }

      public EstimateInt64 InlineCount
      {
        get
        {
          return m_count;
        }
      }

      private List<DataPath> m_result; //null
      private ReadOnlyCollection<DataPath> m_resultReadOnly; //null
      private EstimateInt64 m_count; //null
    }

    #endregion SimpleFetchResult class

    #region ResolveCountData class

    private class ResolveCountData
    {
      public IList<EstimateInt64> DefaultHeadersCounts; //null
      public IList<EstimateInt64> DefaultFootersCounts; //null
      public EstimateInt64 FilteredCollapsedGroupCount; //null
      public IList<EstimateInt64> FilteredGroupCounts; //null
      public EstimateInt64 UnfilteredCollapsedGroupCount; //null
      public IList<EstimateInt64> UnfilteredGroupCounts; //null

      public bool FilteredCollapsedGroupCountIncludeEndPosition; //false
      public int EndPositionItemCollapsedDepth; //0

      public RelativeOffset EndPositionOffset;//null
      public EstimateInt64 TotalCount; //null
    }

    #endregion ResolveCountData class
  }
}
